
// Using lib: libOpenAL32.dll.a

#include "_OpenALEngine.h"

#include <System/Diagnostics/Debug.h>
#include <System/Threading/Thread.h>
#include <System/String.h>

using namespace Riccsson::System;
using namespace Riccsson::System::Threading;
using namespace Riccsson::Xna::Framework::Audio;

// For native audio.
#include <AL/al.h>
#include <AL/alc.h>

const char* al_err_str(ALenum err)
{
    switch(err)
	{
		case AL_NO_ERROR: return "AL_NO_ERROR";
		case AL_INVALID_NAME: return "AL_INVALID_NAME";
        case AL_INVALID_ENUM: return "AL_INVALID_ENUM";
        case AL_INVALID_VALUE: return "AL_INVALID_VALUE";
        case AL_INVALID_OPERATION: return "AL_INVALID_OPERATION";
        case AL_OUT_OF_MEMORY: return "AL_OUT_OF_MEMORY";
    }
    return "unknown";
}

#define __al_check_error(file,line)																						\
do																														\
{																														\
    ALenum err = alGetError();																							\
    for(; err!=AL_NO_ERROR; err=alGetError())																			\
	{																													\
		GC_PTR<String> errorMessage = new String("AL Error ");															\
		/*(**errorMessage) += al_err_str(err) + " at " + file + ":" + line);*/											\
		Riccsson::System::Diagnostics::Debug::WriteLine( errorMessage );												\
    }																													\
}while(0)																												\

void GetALError()
{
	do																														
	{																														
		ALenum err = alGetError();																							
		for(; err!=AL_NO_ERROR; err=alGetError())																			
		{																													
			GC_PTR<String> errorMessage = new String("AL Error ");															
			(**errorMessage) += al_err_str(err);										
			Riccsson::System::Diagnostics::Debug::WriteLine( errorMessage );												
		}																													
	}while(0);
}

#define al_check_error() __al_check_error(__FILE__, __LINE__)

void _OpenALEngine::runBuffer()
{
	//// All sound sources will run each OnBuffer delegate.
	//GC_PTR<Array<GC_PTR<BaseDelegate>>> delegates = OnBuffer.GetInvocationList();
	//
	//for (int i = 0; i < delegates->Count; i++)
	//{
	//	BaseDelegate* baseMethod = (**delegates)[i];
	//
	//	EventHandler* method = (EventHandler*)baseMethod;
	//
	//	// Loop method until we got enough buffer.
	//
	//	method->Invoke(null, null);
	//}
	//

	//// TODO: Implement a better system later.
	//int numberOfDelegates = OnBuffer.GetInvocationList()->Length;
	//if(numberOfDelegates > 1)
	//{
	//	throw System::Exception("Sound engine dont support more than one sound effect instanes. Implement the code above to solve this.");
	//}
	
	// If no sound source is created. Skip to fetch sound buffers.
	if(OnBuffer != null)
	{
		OnBuffer(null, null);
	}
}

void _OpenALEngine::submitBuffer(byte* buffer, int length)
{
	// Submit more data to OpenAL
	alBufferData( BufID, AL_FORMAT_STEREO16, buffer, length * sizeof(byte), 44100 );
}

_OpenALEngine::_OpenALEngine()
	: m_running(false)
	, m_threadRunning(false)
{

}

void _OpenALEngine::initialize()
{
    ALCdevice *dev = null;
    ALCcontext *ctx = null;

    const char *defname = alcGetString(null, ALC_DEFAULT_DEVICE_SPECIFIER);

	GC_PTR<String> defaultDevice = new String("Default device: ");
	(**defaultDevice) += defname;
	Riccsson::System::Diagnostics::Debug::WriteLine( defaultDevice );

    dev = alcOpenDevice(defname);
    ctx = alcCreateContext(dev, null);
    bool success = alcMakeContextCurrent(ctx);
}

void _OpenALEngine::release()
{
    ALCdevice *dev = null;
    ALCcontext *ctx = null;
    ctx = alcGetCurrentContext();
    dev = alcGetContextsDevice(ctx);

    alcMakeContextCurrent(null);
    alcDestroyContext(ctx);
    alcCloseDevice(dev);
}

void _OpenALEngine::play()
{
	
	Thread start(Threading::ThreadStart(this, &_OpenALEngine::threadStart));
	start.Start();
	
	Thread::Sleep(100);

	Threading::Thread thread(Threading::ThreadStart(this, &_OpenALEngine::threadPlaying));
	thread.Start();


}

void _OpenALEngine::pause()
{

}

void _OpenALEngine::stop()
{
	m_running = false;
}

Void _OpenALEngine::threadStart()
{
	if(!m_running && !m_threadRunning)
	{
		ALfloat sourcePos[] = {0,0,0};
		ALfloat sourceVel[] = {0,0,0};
		ALfloat sourceOri[] = {0,0,0,0,0,0};
		alGenSources(1, &FSourceID);
		alSourcefv (FSourceID, AL_POSITION, sourcePos);
		alSourcefv (FSourceID, AL_VELOCITY, sourceVel);
		alSourcefv (FSourceID, AL_DIRECTION, sourceOri);
		GetALError();

		ALuint FBufferID[2];
		alGenBuffers( 2, &FBufferID[0] );
		GetALError();

		// Gain
		ALfloat listenerPos[] = {0,0,0};
		ALfloat listenerVel[] = {0,0,0};
		ALfloat listenerOri[] = {0,0,0,0,0,0};
		alListenerf( AL_GAIN, 1.0 );
		alListenerfv(AL_POSITION, listenerPos);
		alListenerfv(AL_VELOCITY, listenerVel);
		alListenerfv(AL_ORIENTATION, listenerOri);
		GetALError();
	
		alSourceQueueBuffers( FSourceID, 2, &FBufferID[0] );
		GetALError();

		//alSourcei (FSourceID, AL_LOOPING,  AL_TRUE  );
		
		alSourcePlay(FSourceID);
		GetALError();

		m_running = true;
		m_threadRunning = true;
	}

	return Void();
}

Void _OpenALEngine::threadPlaying()
{

	while(m_running)
	{
		// Check how much data is processed in OpenAL's internal queue.
		ALint Processed;
		alGetSourcei( FSourceID, AL_BUFFERS_PROCESSED, &Processed );
		GetALError();

		// Add more buffers while we need them.
		while ( Processed-- )
		{
			alSourceUnqueueBuffers( FSourceID, 1, &BufID );

			runBuffer();
			
			alSourceQueueBuffers( FSourceID, 1, &BufID );
			
			ALint val;
			alGetSourcei(FSourceID, AL_SOURCE_STATE, &val);
            if(val != AL_PLAYING)
			{
                alSourcePlay(FSourceID);
			}
		}

		// Don't kill the CPU.
		Thread::Sleep(1);
	}

	m_threadRunning = false;
		
	return Void();
}